package com.mashuai.utils.toolkit.core.msConverts;

import com.mashuai.utils.toolkit.achieve.MsExegesis;

/**
 * @className: MsCoordinateUtils
 * @Description: TODO
 * @author: 马帅
 * @date: 2025/11/13 15:38
 */

/**
 * 经纬度坐标转换与距离计算工具类
 * 功能：
 * 1. 支持WGS84(GPS)/GCJ-02(高德)/BD-09(百度)坐标系互转
 * 2. 计算两点间直线距离（WGS84坐标系，单位：米，保留两位小数）
 */
public class MsCoordinateUtils {
    // 常量定义
    private static final double X_PI = Math.PI * 3000.0 / 180.0;
    private static final double EE = 0.00669342162296594323;  // WGS84椭球偏心率平方
    private static final double A = 6378245.0;                // WGS84椭球长半轴
    private static final double EARTH_RADIUS = 6371.0;         // 地球平均半径(公里)

    /**
     * 坐标点类（内部使用）
     */
    public static class Point {
        public double lng;  // 经度
        public double lat;  // 纬度

        public Point(double lng, double lat) {
            this.lng = lng;
            this.lat = lat;
        }
    }

    // ========================= 坐标系转换核心方法 ========================= //

    /**
     * WGS84转GCJ-02（高德坐标）
     */
    @MsExegesis("WGS84转GCJ-02（高德坐标）")
    public static Point wgs84ToGcj02(double lng, double lat) {
        if (isOutOfChina(lng, lat)) {
            return new Point(lng, lat);
        }
        double dLat = transformLat(lng - 105.0, lat - 35.0);
        double dLng = transformLng(lng - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * Math.PI;
        double magic = Math.sin(radLat);
        magic = 1 - EE * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * Math.PI);
        dLng = (dLng * 180.0) / (A / sqrtMagic * Math.cos(radLat) * Math.PI);
        return new Point(lng + dLng, lat + dLat);
    }

    /**
     * GCJ-02（高德坐标）转WGS84
     */
    @MsExegesis("GCJ-02（高德坐标）转WGS84")
    public static Point gcj02ToWgs84(double lng, double lat) {
        if (isOutOfChina(lng, lat)) {
            return new Point(lng, lat);
        }
        double dLat = transformLat(lng - 105.0, lat - 35.0);
        double dLng = transformLng(lng - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * Math.PI;
        double magic = Math.sin(radLat);
        magic = 1 - EE * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * Math.PI);
        dLng = (dLng * 180.0) / (A / sqrtMagic * Math.cos(radLat) * Math.PI);
        return new Point(lng * 2 - (lng + dLng), lat * 2 - (lat + dLat));
    }

    /**
     * GCJ-02（高德坐标）转BD-09（百度坐标）
     */
    @MsExegesis("GCJ-02（高德坐标）转BD-09（百度坐标）")
    public static Point gcj02ToBd09(double lng, double lat) {
        double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * X_PI);
        double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * X_PI);
        double bdLng = z * Math.cos(theta) + 0.0065;
        double bdLat = z * Math.sin(theta) + 0.006;
        return new Point(bdLng, bdLat);
    }

    /**
     * BD-09（百度坐标）转GCJ-02（高德坐标）
     */
    @MsExegesis("BD-09（百度坐标）转GCJ-02（高德坐标）")
    public static Point bd09ToGcj02(double lng, double lat) {
        double x = lng - 0.0065;
        double y = lat - 0.006;
        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI);
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI);
        double gcjLng = z * Math.cos(theta);
        double gcjLat = z * Math.sin(theta);
        return new Point(gcjLng, gcjLat);
    }

    /**
     * WGS84转BD-09（百度坐标）
     */
    @MsExegesis("WGS84转BD-09（百度坐标）")
    public static Point wgs84ToBd09(double lng, double lat) {
        Point gcj02 = wgs84ToGcj02(lng, lat);
        return gcj02ToBd09(gcj02.lng, gcj02.lat);
    }

    /**
     * BD-09（百度坐标）转WGS84
     */
    @MsExegesis("BD-09（百度坐标）转WGS84")
    public static Point bd09ToWgs84(double lng, double lat) {
        Point gcj02 = bd09ToGcj02(lng, lat);
        return gcj02ToWgs84(gcj02.lng, gcj02.lat);
    }

    // ========================= 距离计算方法 ========================= //

    /**
     * 计算两点间距离（WGS84坐标系，单位：米，保留两位小数）
     *
     * @param lng1 点1经度
     * @param lat1 点1纬度
     * @param lng2 点2经度
     * @param lat2 点2纬度
     */
    @MsExegesis("计算两点间距离（建议全部传入WGS84坐标系或者全部传入百度坐标系都可以，单位：米，保留两位小数）")
    public static double calculateDistance(double lng1, double lat1, double lng2, double lat2) {
        // 将经纬度转换为弧度
        double radLat1 = Math.toRadians(lat1);
        double radLng1 = Math.toRadians(lng1);
        double radLat2 = Math.toRadians(lat2);
        double radLng2 = Math.toRadians(lng2);

        // Haversine公式计算球面距离
        double dLng = radLng2 - radLng1;
        double dLat = radLat2 - radLat1;
        double a = Math.pow(Math.sin(dLat / 2), 2) +
                Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(dLng / 2), 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        // 转换为米
        double distance = EARTH_RADIUS * c * 1000;
        // 保留两位小数
        return Math.round(distance * 100) / 100.0;
    }

    // ========================= 工具方法 ========================= //

    /**
     * 判断坐标是否在中国大陆以外
     */
    private static boolean isOutOfChina(double lng, double lat) {
        return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;
    }

    /**
     * 纬度转换计算（GCJ-02加密用）
     */
    private static double transformLat(double x, double y) {
        double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y
                + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI)
                + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * Math.PI)
                + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * Math.PI)
                + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * 经度转换计算（GCJ-02加密用）
     */
    private static double transformLng(double x, double y) {
        double ret = 300.0 + x + 2.0 * y + 0.1 * x * x
                + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI)
                + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * Math.PI)
                + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * Math.PI)
                + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
        return ret;
    }


}
